{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sigmoid(x):\n",
    "    z = 1 / (1 + np.exp(-x))\n",
    "    return z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def initialize_params(dims):\n",
    "    W = np.zeros((dims, 1))\n",
    "    b = 0\n",
    "    return W, b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def logistic(X, y, W, b):\n",
    "    num_train = X.shape[0]\n",
    "    num_feature = X.shape[1]\n",
    "    \n",
    "    a = sigmoid(np.dot(X, W) + b)\n",
    "    cost = -1/num_train * np.sum(y*np.log(a) + (1-y)*np.log(1-a))\n",
    "        \n",
    "    dW = np.dot(X.T, (a-y))/num_train\n",
    "    db = np.sum(a-y)/num_train\n",
    "    cost = np.squeeze(cost) \n",
    "    \n",
    "    return a, cost, dW, db"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def logistic_train(X, y, learning_rate, epochs):\n",
    "    # 初始化模型参数\n",
    "    W, b = initialize_params(X.shape[1])  \n",
    "    cost_list = []  \n",
    "    \n",
    "    # 迭代训练\n",
    "    for i in range(epochs):\n",
    "        # 计算当前次的模型计算结果、损失和参数梯度\n",
    "        a, cost, dW, db = logistic(X, y, W, b)    \n",
    "        # 参数更新\n",
    "        W = W -learning_rate * dW\n",
    "        b = b -learning_rate * db        \n",
    "        \n",
    "        # 记录损失\n",
    "        if i % 100 == 0:\n",
    "            cost_list.append(cost)   \n",
    "        # 打印训练过程中的损失 \n",
    "        if i % 100 == 0:\n",
    "            print('epoch %d cost %f' % (i, cost)) \n",
    "               \n",
    "    # 保存参数\n",
    "    params = {            \n",
    "        'W': W,            \n",
    "        'b': b\n",
    "    }        \n",
    "\n",
    "    # 保存梯度\n",
    "    grads = {            \n",
    "        'dW': dW,            \n",
    "        'db': db\n",
    "    }    \n",
    "            \n",
    "    return cost_list, params, grads"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict(X, params):\n",
    "    y_prediction = sigmoid(np.dot(X, params['W']) + params['b'])\n",
    "    \n",
    "    for i in range(len(y_prediction)):        \n",
    "        if y_prediction[i] > 0.5:\n",
    "            y_prediction[i] = 1\n",
    "        else:\n",
    "            y_prediction[i] = 0\n",
    "            \n",
    "    return y_prediction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1b430ad24a8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#生成用于分类的数据集\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.datasets.samples_generator import make_classification\n",
    "X,labels=make_classification(n_samples=100,n_features=2,n_redundant=0,n_informative=2,\n",
    "                             random_state=1,n_clusters_per_class=2)\n",
    "rng=np.random.RandomState(2)\n",
    "X+=2*rng.uniform(size=X.shape)\n",
    "\n",
    "unique_lables=set(labels)\n",
    "colors=plt.cm.Spectral(np.linspace(0,1,len(unique_lables)))\n",
    "for k,col in zip(unique_lables,colors):\n",
    "    x_k=X[labels==k]\n",
    "    plt.plot(x_k[:,0],x_k[:,1],'o',markerfacecolor=col,markeredgecolor=\"k\",\n",
    "             markersize=14)\n",
    "plt.title('data by make_classification()')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(100, 2) (100,)\n"
     ]
    }
   ],
   "source": [
    "print(X.shape, labels.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(100, 3)\n"
     ]
    }
   ],
   "source": [
    "labels = labels.reshape((-1, 1))\n",
    "data = np.concatenate((X, labels), axis=1)\n",
    "print(data.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "X_train= (90, 2)\n",
      "X_test= (10, 2)\n",
      "y_train= (90, 1)\n",
      "y_test= (10, 1)\n"
     ]
    }
   ],
   "source": [
    "# 训练集与测试集的简单划分\n",
    "offset = int(X.shape[0] * 0.9)\n",
    "X_train, y_train = X[:offset], labels[:offset]\n",
    "X_test, y_test = X[offset:], labels[offset:]\n",
    "y_train = y_train.reshape((-1,1))\n",
    "y_test = y_test.reshape((-1,1))\n",
    "\n",
    "print('X_train=', X_train.shape)\n",
    "print('X_test=', X_test.shape)\n",
    "print('y_train=', y_train.shape)\n",
    "print('y_test=', y_test.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 0 cost 0.693147\n",
      "epoch 100 cost 0.554066\n",
      "epoch 200 cost 0.480953\n",
      "epoch 300 cost 0.434738\n",
      "epoch 400 cost 0.402395\n",
      "epoch 500 cost 0.378275\n",
      "epoch 600 cost 0.359468\n",
      "epoch 700 cost 0.344313\n",
      "epoch 800 cost 0.331783\n",
      "epoch 900 cost 0.321216\n"
     ]
    }
   ],
   "source": [
    "cost_list, params, grads = lr_train(X_train, y_train, 0.01, 1000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'W': array([[ 1.55740577],\n",
       "        [-0.46456883]]), 'b': -0.5944518853151362}"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "params"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.]\n",
      " [1.]\n",
      " [1.]\n",
      " [0.]\n",
      " [1.]\n",
      " [1.]\n",
      " [0.]\n",
      " [0.]\n",
      " [1.]\n",
      " [0.]]\n"
     ]
    }
   ],
   "source": [
    "y_prediction = predict(X_test, params)\n",
    "print(y_prediction)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.27936388],\n",
       "       [0.92347241],\n",
       "       [0.90814155],\n",
       "       [0.24192851],\n",
       "       [0.94789076],\n",
       "       [0.98545445],\n",
       "       [0.29242059],\n",
       "       [0.06128463],\n",
       "       [0.95821867],\n",
       "       [0.46214622]])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sigmoid(np.dot(X_test, params['W']) + params['b'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1.]\n",
      " [1.]\n",
      " [0.]\n",
      " [0.]\n",
      " [0.]\n",
      " [1.]\n",
      " [1.]\n",
      " [1.]\n",
      " [1.]\n",
      " [1.]]\n"
     ]
    }
   ],
   "source": [
    "y_train_pred = predict(X_train, params)\n",
    "print(y_train_pred[:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0\n"
     ]
    }
   ],
   "source": [
    "def accuracy(y_test, y_pred):\n",
    "    correct_count = 0\n",
    "    for i in range(len(y_test)):\n",
    "        for j in range(len(y_pred)):\n",
    "            if y_test[i] == y_pred[j] and i == j:\n",
    "                correct_count +=1\n",
    "            \n",
    "    accuracy_score = correct_count / len(y_test)\n",
    "    return accuracy_score\n",
    "\n",
    "accuracy_score_test = accuracy(y_test, y_prediction)\n",
    "print(accuracy_score_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8888888888888888\n"
     ]
    }
   ],
   "source": [
    "accuracy_score_train = accuracy(y_train, y_train_pred)\n",
    "print(accuracy_score_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.31055924594920103"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train[1][1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2.17221697, -0.73380144],\n",
       "       [ 2.54116921,  0.31055925],\n",
       "       [-0.00718884, -0.70554359],\n",
       "       [-0.31285288, -0.17275221],\n",
       "       [-0.67290531,  0.79310561]])"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1b430a37550>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_logistic(X_train, y_train, params):\n",
    "    n = X_train.shape[0]\n",
    "    xcord1 = []\n",
    "    ycord1 = []\n",
    "    xcord2 = []\n",
    "    ycord2 = []\n",
    "    for i in range(n):\n",
    "        if y_train[i] == 1:\n",
    "            xcord1.append(X_train[i][0])\n",
    "            ycord1.append(X_train[i][1])\n",
    "        else:\n",
    "            xcord2.append(X_train[i][0])\n",
    "            ycord2.append(X_train[i][1])\n",
    "    fig = plt.figure()\n",
    "    ax = fig.add_subplot(111)\n",
    "    ax.scatter(xcord1, ycord1,s=32, c='red')\n",
    "    ax.scatter(xcord2, ycord2, s=32, c='green')\n",
    "    x = np.arange(-1.5, 3, 0.1)\n",
    "    y = (-params['b'] - params['W'][0] * x) / params['W'][1]\n",
    "    ax.plot(x, y)\n",
    "    plt.xlabel('X1')\n",
    "    plt.ylabel('X2')\n",
    "    plt.show()\n",
    "    \n",
    "plot_logistic(X_train, y_train, params)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.datasets.samples_generator import make_classification\n",
    "\n",
    "class logistic_regression():\n",
    "    def __init__(self):\n",
    "        pass\n",
    "\n",
    "    def sigmoid(self, x):\n",
    "        z = 1 / (1 + np.exp(-x))\n",
    "        return z\n",
    "\n",
    "    def initialize_params(self, dims):\n",
    "        W = np.zeros((dims, 1))\n",
    "        b = 0\n",
    "        return W, b\n",
    "\n",
    "    def logistic(self, X, y, W, b):\n",
    "        num_train = X.shape[0]\n",
    "        num_feature = X.shape[1]\n",
    "\n",
    "        a = self.sigmoid(np.dot(X, W) + b)\n",
    "        cost = -1 / num_train * np.sum(y * np.log(a) + (1 - y) * np.log(1 - a))\n",
    "\n",
    "        dW = np.dot(X.T, (a - y)) / num_train\n",
    "        db = np.sum(a - y) / num_train\n",
    "        cost = np.squeeze(cost)\n",
    "\n",
    "        return a, cost, dW, db\n",
    "\n",
    "    def logistic_train(self, X, y, learning_rate, epochs):\n",
    "        W, b = self.initialize_params(X.shape[1])\n",
    "        cost_list = []\n",
    "\n",
    "        for i in range(epochs):\n",
    "            a, cost, dW, db = self.logistic(X, y, W, b)\n",
    "            W = W - learning_rate * dW\n",
    "            b = b - learning_rate * db\n",
    "\n",
    "            if i % 100 == 0:\n",
    "                cost_list.append(cost)\n",
    "            if i % 100 == 0:\n",
    "                print('epoch %d cost %f' % (i, cost))\n",
    "\n",
    "        params = {\n",
    "            'W': W,\n",
    "            'b': b\n",
    "        }\n",
    "        grads = {\n",
    "            'dW': dW,\n",
    "            'db': db\n",
    "        }\n",
    "\n",
    "        return cost_list, params, grads\n",
    "\n",
    "    def predict(self, X, params):\n",
    "        y_prediction = self.sigmoid(np.dot(X, params['W']) + params['b'])\n",
    "\n",
    "        for i in range(len(y_prediction)):\n",
    "            if y_prediction[i] > 0.5:\n",
    "                y_prediction[i] = 1\n",
    "            else:\n",
    "                y_prediction[i] = 0\n",
    "\n",
    "        return y_prediction\n",
    "\n",
    "    def accuracy(self, y_test, y_pred):\n",
    "        correct_count = 0\n",
    "        for i in range(len(y_test)):\n",
    "            for j in range(len(y_pred)):\n",
    "                if y_test[i] == y_pred[j] and i == j:\n",
    "                    correct_count += 1\n",
    "\n",
    "        accuracy_score = correct_count / len(y_test)\n",
    "        return accuracy_score\n",
    "\n",
    "    def create_data(self):\n",
    "        X, labels = make_classification(n_samples=100, n_features=2, n_redundant=0, n_informative=2,\n",
    "                                        random_state=1, n_clusters_per_class=2)\n",
    "        labels = labels.reshape((-1, 1))\n",
    "        offset = int(X.shape[0] * 0.9)\n",
    "        X_train, y_train = X[:offset], labels[:offset]\n",
    "        X_test, y_test = X[offset:], labels[offset:]\n",
    "        return X_train, y_train, X_test, y_test\n",
    "\n",
    "    def plot_logistic(self, X_train, y_train, params):\n",
    "        n = X_train.shape[0]\n",
    "        xcord1 = []\n",
    "        ycord1 = []\n",
    "        xcord2 = []\n",
    "        ycord2 = []\n",
    "        for i in range(n):\n",
    "            if y_train[i] == 1:\n",
    "                xcord1.append(X_train[i][0])\n",
    "                ycord1.append(X_train[i][1])\n",
    "            else:\n",
    "                xcord2.append(X_train[i][0])\n",
    "                ycord2.append(X_train[i][1])\n",
    "        fig = plt.figure()\n",
    "        ax = fig.add_subplot(111)\n",
    "        ax.scatter(xcord1, ycord1, s=32, c='red')\n",
    "        ax.scatter(xcord2, ycord2, s=32, c='green')\n",
    "        x = np.arange(-1.5, 3, 0.1)\n",
    "        y = (-params['b'] - params['W'][0] * x) / params['W'][1]\n",
    "        ax.plot(x, y)\n",
    "        plt.xlabel('X1')\n",
    "        plt.ylabel('X2')\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    model = logistic_regression()\n",
    "    X_train, y_train, X_test, y_test = model.create_data()\n",
    "    print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)\n",
    "    cost_list, params, grads = model.logistic_train(X_train, y_train, 0.01, 1000)\n",
    "    print(params)\n",
    "    y_train_pred = model.predict(X_train, params)\n",
    "    accuracy_score_train = model.accuracy(y_train, y_train_pred)\n",
    "    print('train accuracy is:', accuracy_score_train)\n",
    "    y_test_pred = model.predict(X_test, params)\n",
    "    accuracy_score_test = model.accuracy(y_test, y_test_pred)\n",
    "    print('test accuracy is:', accuracy_score_test)\n",
    "    model.plot_logistic(X_train, y_train, params)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
